﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;


namespace EnumUtils
{
  public static class FlagEnumUtilities
  {
    /// <summary>
    /// Bit shifts the flag enum bit pattern leftward.
    /// </summary>
    /// 
    /// <param name="flagEnum">Flag enum instance to bit-shift on (is NOT modified)</param>
    /// 
    /// <param name="timesToShift">The number of times to shift the bit pattern.</param>
    /// 
    /// <returns></returns>
    /// 
    /// <exception cref="ArgumentException">
    /// Thrown when bitPattern is not a Flags enum.
    /// </exception>
    public static TEnum 
    BitShiftLeft<TEnum>( TEnum flagEnum, int timesToShift ) {
      Contract.Requires<ArgumentNullException>(!object.Equals(flagEnum, null));
      Contract.Requires<ArgumentException>(flagEnum is System.Enum);
      Contract.Requires<ArgumentException>(!flagEnum.GetType().IsAbstract, "This method is not contravariant. Please pass in a concrete enum type.");
      Contract.Requires<ArgumentException>(FlagEnumUtilities.DoesEnumHaveFlagsAttribute(flagEnum), ErrorMessageUtils.EnumNotAFlagsEnumMessage);


      int enumAsAnInt = Convert.ToInt32(flagEnum);
      enumAsAnInt <<= timesToShift;
      var intToEnumBitmask = FlagEnumUtilities.GenerateBitmask(flagEnum);

      enumAsAnInt &= intToEnumBitmask;
      TEnum result = FlagEnumUtilities.ConvertIntToEnum<TEnum>(enumAsAnInt);

      return result;
    }


    /// <summary>
    /// Bit shifts the flag enum bit pattern rightward.
    /// </summary>
    /// 
    /// <param name="flagEnum"></param>
    /// 
    /// <param name="timesToShift">The number of times to shift the bit pattern.</param>
    /// 
    /// <returns></returns>
    public static TEnum
    BitShiftRight<TEnum>( TEnum flagEnum, int timesToShift ) {
      Contract.Requires<ArgumentNullException>(!object.Equals(flagEnum, null));
      Contract.Requires<ArgumentException>(flagEnum is System.Enum);
      Contract.Requires<ArgumentException>(!flagEnum.GetType().IsAbstract, "This method is not contravariant. Please pass in a concrete enum type.");
      Contract.Requires<ArgumentException>(FlagEnumUtilities.DoesEnumHaveFlagsAttribute(flagEnum), ErrorMessageUtils.EnumNotAFlagsEnumMessage);

      int enumAsAnInt = Convert.ToInt32(flagEnum);
      enumAsAnInt >>= timesToShift;
      // RJM: Question: what if timesToShift was negative?
      TEnum result = FlagEnumUtilities.ConvertIntToEnum<TEnum>(enumAsAnInt);

      return result;
    }


    public static BitArray ConvertEnumToBitArray<TEnum>( TEnum flagEnum )  {
      Contract.Requires<ArgumentNullException>(!object.Equals(flagEnum, null));
      Contract.Requires<ArgumentException>(flagEnum is System.Enum);

      Type enumFlagsUnderlyingType = Enum.GetUnderlyingType(flagEnum.GetType());
      dynamic enumFlagsBits_integer = Convert.ChangeType(flagEnum, enumFlagsUnderlyingType);
      BitArray enumFlagsBits = new BitArray(BitConverter.GetBytes(enumFlagsBits_integer));
      enumFlagsBits.Length = (int)Math.Ceiling(Math.Log(GenerateBitmask(flagEnum), 2));
      
      return enumFlagsBits;
    }


    public static TEnum
    ConvertIntToEnum<TEnum>(int enumAsAnInt ) {
      Contract.Requires<ArgumentException>(typeof(TEnum).IsSubclassOf(typeof(Enum)));
      
      return (TEnum)Enum.Parse(typeof(TEnum), enumAsAnInt.ToString());
    }


    [Pure]
    public static bool
    DoesEnumHaveFlagsAttribute<TEnum>( TEnum flagEnum ) {
      Contract.Requires<ArgumentNullException>(!object.Equals(flagEnum, null));
      Contract.Requires<ArgumentException>(flagEnum is System.Enum);
      
      bool hasFlagsAttribute = flagEnum.GetType().GetCustomAttributes(false).Any(flag => flag.GetType() == typeof(FlagsAttribute));
      return hasFlagsAttribute;
    }


    /// <summary>
    /// Performs an operation on each flag of a flags enumeration.
    /// </summary>
    /// 
    /// <param name="flagEnum">An enum with a Flags attribute to process.</param>
    /// 
    /// <param name="operation">An operation to execute on the passed enumeration. Must not attempt to do operations that you can't do to an enumeration (like bit-shifting).</param>
    public static void
    ForeachEnumFlag<TEnum>( TEnum flagEnum, Action<TEnum> operation ) {
      Contract.Requires<ArgumentNullException>(!object.Equals(flagEnum, null));
      Contract.Requires<ArgumentNullException>(!object.Equals(operation, default(Action<TEnum>)));
      Contract.Requires<ArgumentException>(flagEnum is System.Enum); 
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(flagEnum), ErrorMessageUtils.EnumNotAFlagsEnumMessage);

      BitArray enumFlagsBits = ConvertEnumToBitArray(flagEnum);
      for (int i = enumFlagsBits.Count - 1; i >= 0; --i) {
        ulong enumAsInteger = Convert.ToUInt64(enumFlagsBits[i]) << i;
        TEnum individualEnumFlag = (TEnum)Enum.Parse(flagEnum.GetType(), enumAsInteger.ToString());
        if (IsFlagDefined(individualEnumFlag)) {
          operation(individualEnumFlag);
        }
      }
    }



    /// <summary>
    /// Tests to determine if an enumeration flag is explicitly defined for an enumeration.
    /// </summary>
    /// 
    /// <param name="flagToTest"></param>
    /// 
    /// <param name="countNoneFlagAsFlag">
    /// Determines whether the None flag should count as a flag being 
    /// defined for the enum. Defaults to false.
    /// </param>
    /// 
    /// <returns></returns>
    /// 
    /// <remarks>
    /// Code based off of a code snippet found on pp. 95 in "C# 3.0 in a 
    /// Nutshell", written by Albahari, J &amp; Albahari, B.
    /// </remarks>
    public static bool
    IsFlagDefined<TEnum>( TEnum flagToTest, bool countNoneFlagAsFlag = false ) {
//TODO: Consolodate common unit tests into helper methods.
      Contract.Requires<ArgumentNullException>(!object.Equals(flagToTest, null));
      Contract.Requires<ArgumentException>(flagToTest is System.Enum); 
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(flagToTest), ErrorMessageUtils.EnumNotAFlagsEnumMessage);

      int dummyInt;
      if (int.TryParse(flagToTest.ToString(), out dummyInt)) {
        return false;
      }
      else {
        switch (flagToTest.ToString()) {
          case "None":
            return countNoneFlagAsFlag ? true : false;
          default:
              // Assume that any other value represents a valid flag
            return true;
        }
      }
    }


    public static int 
    GenerateBitmask<TEnum>( TEnum flagEnum ) {
      Contract.Requires<ArgumentNullException>(!object.Equals(flagEnum, null));
      Contract.Requires<ArgumentException>(flagEnum is System.Enum); 
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(flagEnum));

      var possibleEnumValues = Enum.GetValues(flagEnum.GetType());
      dynamic maxEnumValue = Convert.ChangeType(possibleEnumValues.GetValue(possibleEnumValues.GetUpperBound(0)), Enum.GetUnderlyingType(flagEnum.GetType()));
      dynamic intToEnumBitmask = 1;
      // This is the value of 2^0

      while (intToEnumBitmask <= maxEnumValue) {
        intToEnumBitmask <<= 1;
      }
      --intToEnumBitmask;


      return intToEnumBitmask;
    }



    /// <summary>
    /// Gets an enumerator for a flags enumeration that enumerates through all flag values.
    /// </summary>
    /// 
    /// <param name="flagEnum"></param>
    /// 
    /// <returns></returns>
    public static IEnumerator
    GetEnumerator<TEnum>( TEnum flagEnum ) {
        // RJM: There are no Code Contracts in this method because it simply 
        //      wraps GetGenericEnumerator(flagEnum), which has all the code 
        //      contracts needed. It passes all the regression tests.
      return GetGenericEnumerator(flagEnum);
    }


    /// <summary>
    /// Gets an enumerator for a flags enumeration that enumerates through all flag values.
    /// </summary>
    /// 
    /// <param name="flagEnum"></param>
    /// 
    /// <returns></returns>
    public static IEnumerator<TEnum>
    GetGenericEnumerator<TEnum>( TEnum flagEnum ) {
      Contract.Requires<ArgumentNullException>(!object.Equals(flagEnum, null));
      Contract.Requires<ArgumentException>(flagEnum is System.Enum); 
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(flagEnum), ErrorMessageUtils.EnumNotAFlagsEnumMessage);


      BitArray enumBits = ConvertEnumToBitArray(flagEnum);

      foreach (bool flag in enumBits) {
        TEnum enumFlag = (TEnum)Enum.Parse(flagEnum.GetType(), Convert.ToInt32(flag).ToString());
        if (IsFlagDefined(enumFlag)) {
          yield return enumFlag;
        }
      }

    }



    #region Helper Methods
    #endregion
  }
}
